<template>
  <div class="pu-flow-viewer">
    <div class="pu-flow-viewer-switch" v-if="loadSimulationModule">
      <el-switch title="开启/关闭模拟" v-model="simulationFlag" active-color="#13ce66" inactive-color="#8d9097"
                 @change="toggleSimulationMode"></el-switch>
    </div>
    <div :class="['pu-flow-viewer-toolbar', toolbarPositionControl]" v-if="toolbarDisplay">
      <slot name="toolbar-first"></slot>
      <el-tooltip class="item" effect="dark" content="显示/隐藏序号" :placement="toolbarTipPositionControl" v-if="loadSerialNumber">
        <el-button type="info" icon="el-icon-sort" plain @click="handleSerialNumber()"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="放大" :placement="toolbarTipPositionControl">
        <el-button type="info" icon="el-icon-zoom-in" plain @click="handleZoom(0.1)"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="缩小" :placement="toolbarTipPositionControl">
        <el-button type="info" icon="el-icon-zoom-out" plain @click="handleZoom(-0.1)"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="还原" :placement="toolbarTipPositionControl">
        <el-button type="info" icon="el-icon-refresh" plain @click="handleZoom(0)"></el-button>
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="自适应屏幕并居中" :placement="toolbarTipPositionControl">
        <el-button type="info" icon="el-icon-rank" plain @click="setAdaptiveScreen()"></el-button>
      </el-tooltip>
      <slot name="toolbar-last"></slot>
    </div>

    <div class="pu-flow-viewer-container" ref="flowViewerContainer"></div>
  </div>
</template>

<script>

// 自定义预览
import CustomViewer from "../additional-modules/CustomBpmnViewer";

export default {
  name: "pu-flow-viewer",
  props: {
    xml: String,
    // 加载缩放滚动模块
    loadZoomScrollModule: {
      type: Boolean,
      default: true
    },
    // 加载拖拽模块
    loadMoveCanvasModule: {
      type: Boolean,
      default: true
    },
    // 加载模拟模块
    loadSimulationModule: Boolean,
    // 加载小地图模块
    loadMinimapModule: Boolean,
    // 打开模拟
    openSimulation: {
      type: Boolean,
      default: true
    },
    // 打开小地图
    openMinimap: {
      type: Boolean,
      default: true
    },
    // 适应屏幕默认是否开启
    adaptiveScreen: Boolean,
    // 加载序号
    loadSerialNumber: Boolean,
    // 显示序号
    openSerialNumber: Boolean,
    // 工具栏显隐
    toolbarDisplay: {
      type: Boolean,
      default: true
    },
    // 工具栏位置
    // top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end
    toolbarPosition: {
      type: String,
      default() {
        return 'left';
      }
    },
    // 设置节点颜色
    nodeColors: {
      type: Array,
      default() {
        // [{id: "节点id", fill: "red", stroke: ""}]
        return [];
      }
    },
    // 节点信息
    nodeInfos: {
      type: Array,
      default() {
        // [{id: "节点id", html: ""}]
        return [];
      }
    },
    // 节点信息样式全局配置
    nodeInfoStyle: {
      type: Object,
      default() {
        return {};
      }
    },
    // 模块扩展
    modules: {
      type: Array,
      default() {
        return [];
      }
    },
  },
  data() {
    return {
      bpmnViewer: null,
      simulationFlag: false,
      serialNumberFlag: false,
      serialNumberOverlaysIds: [],
      scale: 1,
      modeling: null,
      elementRegistry: null,
      overlays: null,
      setNodeColorTimer: null,
      setNodeInfoTimer: null,
      defaultNodeInfoStyle: {
        bottom: -15,
        left: -5,
        class: "",
        style: "",
        width: '300px',
        minWidth: '100px',
        height: '200px',
        minHeight: '100px',
        border: "2px solid #409eff",
        padding: "5px"
      }
    }
  },
  computed: {
    toolbarPositionControl() {
      return `pu-flow-viewer-toolbar__${this.toolbarPosition}`;
    },
    toolbarTipPositionControl() {
      if (this.toolbarPosition.startsWith('top')) {
        return this.toolbarPosition.replace("top", "bottom");
      } else if (this.toolbarPosition.startsWith('bottom')) {
        return this.toolbarPosition.replace("bottom", "top");
      } else if (this.toolbarPosition.startsWith('left')) {
        return this.toolbarPosition.replace("left", "right");
      } else if (this.toolbarPosition.startsWith('right')) {
        return this.toolbarPosition.replace("right", "left");
      }
      return "left";
    },
  },
  watch: {
    xml: {
      immediate: true,
      handler(val) {
        this.$nextTick(() => this.importXml(val));
      }
    },
    openSimulation: {
      immediate: true,
      handler(val) {
        this.$nextTick(() => this.simulationFlag = val);
      }
    },
    nodeColors: {
      immediate: true,
      deep: true,
      handler(val) {
        this.$nextTick(() => this.setNodeColor(val));
      }
    },
    nodeInfos: {
      immediate: true,
      deep: true,
      handler(val) {
        this.$nextTick(() => this.setNodeInfo(val));
      }
    }
  },
  mounted() {
    this.initBpmnViewer();
  },
  methods: {
    async importXml(xml) {
      // 加载xml
      if (xml) {
        let {warnings} = await this.bpmnViewer.importXML(this.xml);
        if (warnings && warnings.length) {
          warnings.forEach(warn => console.warn(warn));
        }
        // 自适应屏幕
        if (this.adaptiveScreen) {
          this.setAdaptiveScreen();
        }
        // 主动调用一次
        this.setNodeColor(this.nodeColors);
        this.setNodeInfo(this.nodeInfos);

        // 显示序号
        if(this.loadSerialNumber && this.openSerialNumber){
          this.serialNumberFlag = true;
          this.setSerialNumber(this.nodeInfos);
        }
      }
    },
    initBpmnViewer() {
      this.scale = 1;
      try {
        // 避免缓存，每次清一下
        this.bpmnViewer && this.bpmnViewer.destroy();

        // 创建视图
        this.bpmnViewer = new CustomViewer({
          container: this.$refs.flowViewerContainer,
        }, {
          loadZoomScrollModule: this.loadZoomScrollModule,
          loadMoveCanvasModule: this.loadMoveCanvasModule,
          loadSimulationModule: this.loadSimulationModule,
          loadMinimapModule: this.loadMinimapModule,
          modules: this.modules
        });

        this.modeling = this.bpmnViewer.get("modeling");
        this.elementRegistry = this.bpmnViewer.get("elementRegistry");
        this.overlays = this.bpmnViewer.get('overlays');

        // 打开小地图
        if (this.loadMinimapModule && this.openMinimap) {
          this.bpmnViewer.get("minimap").open();
        }
        // 打开模拟
        if (this.loadSimulationModule && this.openSimulation) {
          this.toggleSimulationMode();
        }

        // 初始化监听器
        this.initModelListeners();
      } catch (e) {
        console.error(`[Process Viewer Warn]: Viewer初始化: ${e?.message || e}`);
      }
    },
    // 初始化监听器
    initModelListeners() {
      // 节点选中
      this.bpmnViewer.on("selection.changed", ({newSelection, oldSelection}) => {
        // 添加新节点的选中效果
        if (newSelection[0] && newSelection[0].type !== "bpmn:Process") {
          let element1 = document.querySelector(".pu-flow-viewer-container")
              .querySelector(`div[data-container-id=${newSelection[0].businessObject.id}]`)?.querySelector(".djs-overlay-flow-info");
          if (element1) {
            element1.classList.add("selected");
          }
        }
        // 移除旧节点的选中效果
        if (oldSelection[0] && oldSelection[0].type !== "bpmn:Process") {
          let element1 = document.querySelector(".pu-flow-viewer-container")
              .querySelector(`div[data-container-id=${oldSelection[0].businessObject.id}]`)?.querySelector(".djs-overlay-flow-info");
          if (element1) {
            element1.classList.remove("selected");
          }
        }
        this.$emit("selection-changed", {newSelection, oldSelection});
      })
      // hover控制
      this.bpmnViewer.on("element.hover", (event, element) => {
        if (event.element.type && event.element.type !== "bpmn:Process") {
          let element1 = document.querySelector(".pu-flow-viewer-container")
              .querySelector(`div[data-container-id=${element.element.businessObject.id}]`)?.querySelector(".djs-overlay-flow-info");
          if (element1) {
            element1.classList.add("hover");
          }
        }
        this.$emit("element-hover", event, element);
      })
      // out控制
      this.bpmnViewer.on("element.out", (event, element) => {
        if (event.element.type && event.element.type !== "bpmn:Process") {
          let element1 = document.querySelector(".pu-flow-viewer-container")
              .querySelector(`div[data-container-id=${element.element.businessObject.id}]`)?.querySelector(".djs-overlay-flow-info");
          if (element1) {
            element1.classList.remove("hover");
          }
        }
        this.$emit("element-out", event, element);
      })
    },
    // 放大、缩小、还原
    handleZoom(radio) {
      const newScale = !radio ? 1.0 : this.scale + radio;
      this.bpmnViewer.get("canvas").zoom(newScale);
      this.scale = newScale;
    },
    handleSerialNumber() {
      this.serialNumberFlag = !this.serialNumberFlag;
      if (this.serialNumberFlag) {
        this.setSerialNumber(this.nodeInfos);
      } else {
        this.serialNumberOverlaysIds.forEach(overlaysId => {
          this.overlays.remove(overlaysId);
        })
      }
    },
    // 自适应屏幕
    setAdaptiveScreen() {
      this.scale = 1;
      this.bpmnViewer.get("canvas").zoom("fit-viewport", "auto");
    },
    // 开启/关闭模拟
    toggleSimulationMode() {
      this.bpmnViewer.get("toggleMode").toggleMode();
    },
    // 获取viewer原型
    getBpmnViewer() {
      return this.bpmnViewer;
    },
    // 获取Overlays
    getOverlays() {
      return this.overlays;
    },
    // 设置节点颜色
    setNodeColor(nodeColors = []) {
      if (!this.elementRegistry || !this.modeling) {
        return;
      }
      nodeColors.forEach(nodeColor => {
        try {
          // 设置节点颜色
          let moduleElement = this.elementRegistry.get(nodeColor.id);
          if (moduleElement) {
            this.modeling.setColor(moduleElement, {fill: nodeColor.fill, stroke: nodeColor.stroke});
          } else {
            console.warn(`[Process Viewer Warn]: 设置节点颜色: 节点[${nodeColor.id}]不存在，跳过~`);
          }
        } catch (e) {
          console.error(`[Process Viewer Warn]: 设置节点颜色: ${e?.message || e}，跳过~`);
        }
      })
    },
    // 设置节点信息
    setNodeInfo(nodeInfos = []) {
      if (!this.overlays) {
        return;
      }
      nodeInfos.forEach(nodeInfo => {
        // 校验节点id
        if (!nodeInfo.id) {
          console.warn(`[Process Viewer Warn]: 设置节点信息: 未配置节点id ${JSON.stringify(nodeInfo)}，跳过~`);
          return;
        }
        let moduleElement = this.elementRegistry.get(nodeInfo.id);
        if (!moduleElement) {
          console.warn(`[Process Viewer Warn]: 设置节点信息: 节点[${nodeInfo.id}]不存在，跳过~`);
          return;
        }

        // 设置节点class
        if (nodeInfo.customClass) {
          const canvas = this.bpmnViewer.get("canvas");
          let classArrays = nodeInfo.customClass.split(",");
          for (let classArraysKey in classArrays) {
            let classArray = classArrays[classArraysKey];
            canvas.addMarker(nodeInfo.id, classArray)
          }
        }

        // 兼容设置节点颜色
        this.setNodeColor(nodeInfos);

        // 添加节点comment
        this.addComment(nodeInfo);
        // 添加节点浮框信息
        if (!nodeInfo.html) {
          return;
        }
        this.addOverlay(nodeInfo);
      })

      // 设置进行中的节点动画
      const n = document.querySelectorAll(".node-in-progress");
      n.forEach(e => {
        const element = e.querySelector(".djs-visual rect");
        element && element.setAttribute("stroke-dasharray","4,4");
      })
    },
    // 添加自定义浮框
    addOverlay(nodeInfo) {
      if (Array.isArray(nodeInfo)) {
        nodeInfo.forEach(item => {
          this.addOverlay(item);
        })
        return;
      }
      // 节点样式
      const nodeInfoStyle = Object.assign(this.defaultNodeInfoStyle, this.nodeInfoStyle, nodeInfo.style);

      // 创建流程节点信息容器
      let nodeInfoEl = document.createElement('div');
      nodeInfoEl.setAttribute("class", "djs-overlay-flow-info");
      if (nodeInfoStyle.class) {
        nodeInfoEl.classList.add(nodeInfoStyle.class)
      }
      if (nodeInfoStyle.style) {
        nodeInfoEl.style = nodeInfoStyle.style;
      }
      nodeInfoEl.style.width = nodeInfoStyle.width;
      nodeInfoEl.style.minWidth = nodeInfoStyle.minWidth;
      nodeInfoEl.style.height = nodeInfoStyle.height;
      nodeInfoEl.style.minHeight = nodeInfoStyle.minHeight;
      nodeInfoEl.style.border = nodeInfoStyle.border;
      nodeInfoEl.style.padding = nodeInfoStyle.padding;

      // 处理dom
      if (typeof nodeInfo.html === "object") {
        if (nodeInfo instanceof HTMLElement) {
          nodeInfoEl.appendChild(nodeInfo.htlm);
        } else {
          console.warn(`[Process Viewer Warn]: 设置节点信息: 节点信息html参数类型不支持！ [${typeof nodeInfo.html}]，跳过~`);
          return;
        }
      }
      // 处理html
      else if (typeof nodeInfo.html === "string") {
        nodeInfoEl.innerHTML = nodeInfo.html;
      }
      // 异常类型处理
      else {
        console.warn(`[Process Viewer Warn]: 设置节点信息: 节点信息html参数类型不支持！ [${typeof nodeInfo.html}]，跳过~`);
        return;
      }

      // 构建浮动框位置对象
      let position = {};
      if (nodeInfoStyle.top) {
        position.top = nodeInfoStyle.top;
      }
      if (nodeInfoStyle.bottom) {
        position.bottom = nodeInfoStyle.bottom;
      }
      if (nodeInfoStyle.left) {
        position.left = nodeInfoStyle.left;
      }
      if (nodeInfoStyle.right) {
        position.right = nodeInfoStyle.right;
      }
      // 添加节点信息
      this.overlays.add(nodeInfo.id, {
        position: position,
        html: nodeInfoEl
      });
    },
    // 添加下方浮框
    addComment(nodeInfo) {
      if (Array.isArray(nodeInfo)) {
        nodeInfo.forEach(item => {
          this.addComment(item);
        })
      } else if (typeof nodeInfo === 'object') {
        // comments
        if (!nodeInfo.comments || !Array.isArray(nodeInfo.comments) || nodeInfo.comments.length === 0) {
          return;
        }
        // 校验节点id
        if (!nodeInfo.id) {
          console.warn(`[Process Viewer Warn]: 设置节点comment: 未配置节点id ${JSON.stringify(nodeInfo)}，跳过~`);
          return;
        }
        let moduleElement = this.elementRegistry.get(nodeInfo.id);
        if (!moduleElement) {
          console.warn(`[Process Viewer Warn]: 设置节点comment: 节点[${nodeInfo.id}]不存在，跳过~`);
          return;
        }
        let position = {};
        if (moduleElement.type === 'bpmn:StartEvent' || moduleElement.type === 'bpmn:EndEvent') {
          position = {bottom: -34, left: -47};
        } else {
          position = {bottom: -12, left: -15};
        }
        //
        let text = '';
        nodeInfo.comments.forEach(comment => {
          text = text.concat(`<div class="pu-flow-viewer__comment-div"><span title="${comment.replace(/<[^>]+>/g, "")}">${comment}</spcan></div>`);
        })
        // 添加节点信息
        this.overlays.add(nodeInfo.id, {
          position: position,
          html: `<div class="pu-flow-viewer__comment">${text}</div>`
        });
      } else {
        console.warn(`[Process Viewer Warn]: 设置节点comment: 不支持的数据类型 ${typeof nodeInfo}, 数据结构： ${JSON.stringify(nodeInfo)}，跳过~`);
      }
    },
    setSerialNumber(nodeInfos = []) {
      this.serialNumberOverlaysIds.forEach(overlaysId => {
        this.overlays.remove(overlaysId);
      })

      let serialNumberObj = {};
      nodeInfos.forEach(nodeInfo => {
        if (!nodeInfo.sort) {
          return;
        }
        if (serialNumberObj[nodeInfo.id]) {
          serialNumberObj[nodeInfo.id].push(nodeInfo.sort);
        } else {
          serialNumberObj[nodeInfo.id] = [nodeInfo.sort];
        }
      })

      Object.keys(serialNumberObj).forEach(key => {
        let serialNumberObjElement = serialNumberObj[key];
        let text = '';
        serialNumberObjElement.forEach(e => {
          text = text.concat(`<span class="pu-flow-viewer__sort-tag">${e}</span>`);
        })

        let position = {top: -12, left: -12};
        let overlaysId = this.overlays.add(key, {
          position: position,
          html: `<div class="pu-flow-viewer__sort">${text}</div>`
        });
        this.serialNumberOverlaysIds.push(overlaysId);
      })
    }
  },
  beforeDestroy() {
    this.bpmnViewer && this.bpmnViewer.destroy();
  }
}
</script>

<style lang="scss">

.pu-flow-viewer {
  height: 100%;
  position: relative;
  overflow: hidden;

  .bjs-container {
    .bts-toggle-mode {
      display: none;
    }
  }

  &-switch {
    display: inline-block;
    position: absolute;
    top: 20px;
    left: 20px;
    z-index: 101;
  }

  &-toolbar {
    position: absolute;
    z-index: 101;


    // top/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-end
    &__top {
      top: 20px;

      &-start {
        top: 20px;
        left: 20px;
      }

      &-end {
        top: 20px;
        right: 20px;
      }
    }

    &__bottom {
      bottom: 20px;

      &-start {
        bottom: 20px;
        left: 20px;
      }

      &-end {
        bottom: 20px;
        right: 20px;
      }
    }

    &__top, &__bottom {
      left: 50%;
      transform: translateX(-50%);
    }

    &__right {
      right: 20px;

      &-start {
        right: 20px;
        top: 20px;
      }

      &-end {
        right: 20px;
        bottom: 20px;
      }
    }

    &__left {
      left: 20px;

      &-start {
        left: 20px;
        top: 20px;
      }

      &-end {
        left: 20px;
        bottom: 20px;
      }
    }

    &__left, &__right {
      top: 50%;
      transform: translateY(-50%);
    }

    &__left, &__left-start, &__left-end, &__right, &__right-start, &__right-end {
      display: flex;
      flex-direction: column;

      .el-button {
        margin-left: 0 !important;
      }

      .el-button + .el-button {
        margin-top: 10px;
      }
    }


    button {
      padding: 6px 8px;

      i {
        font-size: 18px;
      }
    }

  }

  /* 容器高度、背景 */
  &-container {
    height: 100%;
    width: 100%;
    background: url('') repeat !important;


    .bjs-powered-by {
      display: none;
    }
  }

  // , .djs-element.selected .djs-outline
  .djs-element.hover .djs-outline {
    visibility: visible;
    shape-rendering: geometricPrecision;
    stroke-dasharray: 3, 3;
    stroke: var(--element-hover-outline-fill-color);
    stroke-width: 1px
  }

  .djs-element.selected .djs-outline {
    visibility: visible;
    shape-rendering: geometricPrecision;
    stroke-dasharray: 3, 3;
    stroke: var(--element-selected-outline-stroke-color);
    stroke-width: 2px
  }

  .node-primary:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: #409eff !important
  }

  .node-success:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: #67c23a !important
  }

  .node-warn:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: #e6a23c !important
  }

  .node-error:not(.djs-connection) .djs-visual > :nth-child(1) {
    fill: #f56c6c !important
  }

  .line-primary:not(.djs-shape) .djs-visual :last-child {
    stroke: #409eff !important
  }

  .line-success:not(.djs-shape) .djs-visual :last-child {
    stroke: #67c23a !important
  }

  .line-warn:not(.djs-shape) .djs-visual :last-child {
    stroke: #e6a23c !important
  }

  .line-error:not(.djs-shape) .djs-visual :last-child {
    stroke: #f56c6c !important
  }

  @keyframes dynamicNode {
    to {
      stroke-dashoffset: 100%
    }
  }

  .node-in-progress .djs-visual {
    animation: dynamicNode 30S linear infinite;
    -webkit-animation: dynamicNode 30S linear infinite;
    -webkit-animation-fill-mode: forwards;
  }

  .djs-overlay-flow-info {
    position: relative;
    font-size: 14px;
    background-color: white;
    display: none;
  }

  .djs-overlay-flow-info.hover {
    display: block;
    z-index: 106;
  }

  .djs-overlay-flow-info.selected {
    display: block;
    z-index: 105;
  }

  &__comment {
    width: 130px;
    text-align: center;
    font-size: 13px;
    border: 2px dashed transparent;
    border-radius: 6px;
    cursor: default;

    &-div {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    &:hover {
      border: 2px dashed var(--color-blue-205-100-70);
    }
  }

  &__sort {
    display: flex;

    &-tag {
      display: inline-block;
      width: 15px;
      height: 15px;
      color: red;
      font-size: 12px;
      text-align: center;
      border: 1px solid #f72200;
      border-radius: 100%;
      background-color: #ffdddd;
    }

    &-tag+&-tag {
      margin-left: 2px;
    }

  }

}

</style>
